﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the Microsoft Public License (Ms-PL).
// See accompanying file License.txt or copy at http://mews.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Mews.ServerTester
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;

    using Mews.Net;
    using Mews.Net.Nntp;
    using Mews.Net.Nntp.Cache;

    /// <summary>The main form of the program.</summary>
    internal sealed partial class MainForm : Form, ISendReceiveProgressObserver
    {
        private readonly Action<string> setProgressText;
        private readonly Cache cache;

        private Client client;

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>See <see cref="ISendReceiveProgressObserver.OnConnecting"/>.</summary>
        public IDisposable OnConnecting(string hostName)
        {
            this.SetText("Connecting to {0} ...", hostName);
            return new AlwaysOnNetworkConnection();
        }

        /// <summary>See <see cref="ISendReceiveProgressObserver.OnConnected"/>.</summary>
        public void OnConnected(string hostName)
        {
            this.SetText("Connected to {0}.", hostName);
        }

        /// <summary>See <see cref="ISendReceiveProgressObserver.OnSendProgressChanged"/>.</summary>
        public void OnSendProgressChanged(int percent)
        {
            this.SetText("Sending: {0}", percent);
        }

        /// <summary>See <see cref="ISendReceiveProgressObserver.OnHeadersProgressChanged"/>.</summary>
        public void OnHeadersProgressChanged(string groupDisplayName, int percent)
        {
            this.SetText("{0} Headers: {1}%", groupDisplayName, percent);
        }

        /// <summary>See <see cref="ISendReceiveProgressObserver.OnBodiesProgressChanged"/>.</summary>
        public void OnBodiesProgressChanged(string groupDisplayName, int percent)
        {
            this.SetText("{0} Bodies: {1}%", groupDisplayName, percent);
        }

        /// <summary>See <see cref="ISendReceiveProgressObserver.OnFinished"/>.</summary>
        public void OnFinished()
        {
            this.SetText("Send/Receive finished.");
        }

        /// <summary>See <see cref="ISendReceiveProgressObserver.OnStopped"/>.</summary>
        public void OnStopped()
        {
            this.SetText("Send/Receive stopped.");
        }

        /// <summary>See <see cref="ISendReceiveProgressObserver.OnError"/>.</summary>
        public bool OnError(string hostName, string groupName, Exception ex)
        {
            this.SetText(ex.ToString());
            return true;
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        internal MainForm()
        {
            this.setProgressText = text => this.progressTextBox.Text = text;

            var dataDirectory = Path.Combine(
                Path.GetDirectoryName(typeof(MainForm).Assembly.ManifestModule.FullyQualifiedName),
                Guid.NewGuid().ToString("N"));
            Cache.CreateCache(dataDirectory);
            this.cache = new Cache(dataDirectory, new NullCryptoProvider());
            this.InitializeComponent();

            this.hostNameTextBox.Text = Properties.Settings.Default.HostName;
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>Clean up any resources being used.</summary>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.components != null)
                {
                    this.components.Dispose();
                }

                if (this.client != null)
                {
                    this.client.Dispose();
                }

                this.cache.Dispose();
            }

            base.Dispose(disposing);
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        private void OnCapabilitiesButton_Click(object sender, EventArgs e)
        {
            this.ShowMultilineResponse<CapabilitiesResponse, string>(new CapabilitiesCommand(), line => line);
        }

        private void OnHelpButton_Click(object sender, EventArgs e)
        {
            this.ShowResponse(new HelpCommand(), response => response.HelpText);
        }

        private void OnListOverviewFormatButton_Click(object sender, EventArgs e)
        {
            this.ShowMultilineResponse<ListOverviewFmtResponse, string>(new ListOverviewFmtCommand(), line => line);
        }

        private void OnListNewsgroupsButton_Click(object sender, EventArgs e)
        {
            this.ShowMultilineResponse<ListNewsgroupsResponse, GroupInfo>(
                new ListNewsgroupsCommand(this.wildmatTextBox.Text), info => info.Name + " " + info.Description);
        }

        private void OnListActiveButton_Click(object sender, EventArgs e)
        {
            Func<ListActiveResponse, string> convertToString = response =>
                {
                    List<ActiveGroupInfo> activeGroups = new List<ActiveGroupInfo>(1024);

                    using (response)
                    {
                        while (response.MoveNext())
                        {
                            activeGroups.Add(response.Current);
                        }
                    }

                    var lines =
                        (from activeGroup in activeGroups
                         let articleCount = activeGroup.HighWaterMark - activeGroup.LowWaterMark
                         orderby articleCount descending
                         select activeGroup.GroupName + " " + articleCount.ToString()).ToArray();
                    return string.Join("\r\n", lines);
                };

            this.ShowResponse<ListActiveResponse>(new ListActiveCommand(), response => convertToString(response));
        }

        private void OnStartButton_Click(object sender, EventArgs e)
        {
            foreach (var host in this.cache.GetHosts())
            {
                host.Delete();
            }

            if (this.cache.GetHosts().Count() == 0)
            {
                string largestGroupName = this.GetLargestGroupName();

                if (largestGroupName != null)
                {
                    var host = this.cache.AddHost(
                        this.hostNameTextBox.Text,
                        119,
                        null,
                        null,
                        this.usernameTextBox.Text,
                        this.passwordTextBox.Text);
                    host.AddGroup(largestGroupName, largestGroupName, 10000, 10000);
                }
                else
                {
                    this.SetText("No non-empty group found!");
                    return;
                }
            }

            this.cache.StartSendReceive(new IGroup[] { this.cache.GetHosts().First().GetGroups().First() }, true, this);
        }

        private void SetText(string format, params object[] args)
        {
            this.progressTextBox.BeginInvoke(
                this.setProgressText, string.Format(CultureInfo.InvariantCulture, format, args));
        }

        private void ShowMultilineResponse<TResponse, TLine>(
            CommandBase<Client, TResponse> command, Func<TLine, string> lineToString)
            where TResponse : IResponse, IEnumerator<TLine>
        {
            this.ShowResponse(command, response => ConcatenateLines(response, lineToString));
        }

        private void ShowResponse<TResponse>(
            CommandBase<Client, TResponse> command, Func<TResponse, string> toString) where TResponse : IResponse
        {
            var response = this.Execute(command);

            if (response != null)
            {
                this.SetText(toString(response));
            }
        }

        private string GetLargestGroupName()
        {
            long largestArticleCount = 0;
            string largestGroupName = null;

            using (var response = this.Execute(new ListActiveCommand()))
            {
                if (response != null)
                {
                    while (response.MoveNext())
                    {
                        var articleCount = response.Current.HighWaterMark - response.Current.LowWaterMark + 1;

                        if (articleCount > largestArticleCount)
                        {
                            largestArticleCount = articleCount;
                            largestGroupName = response.Current.GroupName;
                        }
                    }
                }
            }

            if (largestGroupName == null)
            {
                // Fallback for when ListActive does not return something useful
                using (var response = this.Execute(new ListNewsgroupsCommand()))
                {
                    while (response.MoveNext())
                    {
                        this.client.EnqueueCommand(new GroupCommand(response.Current.Name));
                    }
                }

                while (this.client.PendingCommandsCount > 0)
                {
                    var groupResponse = (GroupResponse)this.client.GetResponse();

                    if (groupResponse.ArticleCount > largestArticleCount)
                    {
                        largestArticleCount = groupResponse.ArticleCount;
                        largestGroupName = groupResponse.GroupName;
                    }
                }
            }

            return largestGroupName;
        }

        private TResponse Execute<TResponse>(CommandBase<Client, TResponse> command) where TResponse : IResponse
        {
            Cursor oldCursor = this.Cursor;
            this.Cursor = Cursors.WaitCursor;

            try
            {
                if ((this.client == null) || (Properties.Settings.Default.HostName != this.hostNameTextBox.Text))
                {
                    if (this.client != null)
                    {
                        this.client.Dispose();
                        this.client = null;
                    }

                    this.client = new Client(this.hostNameTextBox.Text);

                    if (!string.IsNullOrEmpty(this.usernameTextBox.Text))
                    {
                        new AuthInfoCommand(this.usernameTextBox.Text, this.passwordTextBox.Text).Execute(this.client);
                    }

                    Properties.Settings.Default.HostName = this.hostNameTextBox.Text;
                    Properties.Settings.Default.Save();
                    this.SetText("Connected to {0}.", Properties.Settings.Default.HostName);
                }

                try
                {
                    return command.Execute(this.client);
                }
                catch (Exception ex)
                {
                    if ((ex is FeatureNotSupportedException) || (ex is IOException) ||
                        (ex is MissingAuthenticationException) || (ex is ServiceUnavailableException) ||
                        (ex is UnknownCommandException))
                    {
                        this.SetText(ex.ToString());
                        return default(TResponse);
                    }
                    else
                    {
                        throw;
                    }
                }
            }
            finally
            {
                this.Cursor = oldCursor;
            }
        }

        private static string ConcatenateLines<TLine>(IEnumerator<TLine> lines, Func<TLine, string> toString)
        {
            using (lines)
            {
                var stringBuilder = new StringBuilder();

                if (lines != null)
                {
                    while (lines.MoveNext())
                    {
                        stringBuilder.AppendLine(toString(lines.Current));
                    }
                }

                return stringBuilder.ToString();
            }
        }

        /// <summary>Represents an always-on network connection.</summary>
        private sealed class AlwaysOnNetworkConnection : IDisposable
        {
            /// <summary>See <see cref="IDisposable.Dispose"/>.</summary>
            public void Dispose()
            {
            }
        }

        /// <summary>Represents a crypto provider that does not encrypt.</summary>
        private sealed class NullCryptoProvider : ICryptoProvider
        {
            /// <summary>See <see cref="ICryptoProvider.Encrypt"/>.</summary>
            public byte[] Encrypt(byte[] input)
            {
                return input;
            }

            /// <summary>See <see cref="ICryptoProvider.Decrypt"/>.</summary>
            public byte[] Decrypt(byte[] input)
            {
                return input;
            }
        }
    }
}
